import TJavaLib.TList;
import TJavaLib.TString;
import TJavaLib.TPoint;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.FileOutputStream;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.util.Random;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.text.DecimalFormat;

public class TNetworkView extends TView {
	private JPopupMenu popNetwork = new JPopupMenu();
	private JMenuItem[] popSep = new JMenuItem[3];
	
	private JMenuItem popNetUtility = new JMenuItem("Network utilities:");
	private JMenuItem popNewGateway = new JMenuItem("   New gateway");
	private JMenuItem popNewSensors = new JMenuItem("   New sensors");
	private JMenuItem popSaveScene = new JMenuItem("   Save scenario");
	private JMenuItem popLoadScene = new JMenuItem("   Load scenario");
	
	private JPopupMenu popSensor = new JPopupMenu();
	private JMenuItem popSensorUtility = new JMenuItem("Sensor utilities:");
	private JMenuItem popSetDes = new JMenuItem("   Set as destination");
	private JMenuItem popSetNor = new JMenuItem("   Set as normal");
	private JMenuItem popChangeAnno = new JMenuItem("   Change annotation");
	private JMenuItem popDelSen = new JMenuItem("   Delete this sensor");
	private DecimalFormat dF = new DecimalFormat("#,##0.00", new DecimalFormatSymbols(Locale.US));
	
	private TNetwork network;
	private int simW, simH, xMoveView, yMoveView;
	private double ratioW, ratioH, xZoom, yZoom, netW, netH;
	private TList net;
	private Random random;
	
	private TNode clickNode, deployNode;
	private int xM_Old, yM_Old, currentX, currentY;
	private boolean deployingNode, draggingNode, movingView;
	
	public TNetworkView(TNetwork network, String Title) {
		super(TView.NETWORK, Title);
		setPreferredSize(new Dimension(700, 610));
		setBackground(Color.WHITE);
		
		this.network = network;
		net = network.getNetwork();
		netW = network.getDepW();
		netH = network.getDepH();
		xZoom = netW / 2;
		yZoom = netH / 2;
		
		clickNode = null;
		deployNode = null;
		deployingNode = false;
		draggingNode = false;
		movingView = false;
		random = new Random();
		
		//Initialize components
		frame.setClosable(false);
		popNetwork.add(popNetUtility);
		popNetwork.add(popNewGateway);
		popNetwork.add(popNewSensors);
		popSep[0] = new JMenuItem("   --------");
		popSep[0].setEnabled(false);
		popNetwork.add(popSep[0]);
		popNetwork.add(popLoadScene);
		popNetwork.add(popSaveScene);
		
		popSensor.add(popSensorUtility);
		popSensor.add(popSetDes);
		popSensor.add(popSetNor);
		popSensor.add(popChangeAnno);
		popSep[1] = new JMenuItem("   --------");
		popSep[1].setEnabled(false);
		popSensor.add(popSep[1]);
		popSensor.add(popDelSen);
		
		//Resize response
		addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent evt) {	netViewResized(evt); } });
		
		//Mouse events for the netView
		addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent evt) { netViewMousePressed(evt); }
			public void mouseReleased(MouseEvent evt) { netViewMouseReleased(evt); }
			public void mouseClicked(MouseEvent evt) { netViewMouseClicked(evt); }
		});
		
		addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseDragged(MouseEvent evt) { netViewMouseDragged(evt); }
			public void mouseMoved(MouseEvent evt) { netViewMouseMoved(evt); }
		});
		
		addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent evt) { netViewMouseWheeled(evt); }
		});
		
		//Popup menu
    popNewSensors.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popNewSensorsClick(evt); } });
		popNewGateway.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popNewGatewayClick(evt); } });
		popSaveScene.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popSaveSceneClick(evt); } });
		popLoadScene.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popLoadSceneClick(evt); } });
			
		popSetDes.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popSetDesClick(evt); } });
		popSetNor.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popSetNorClick(evt); } });
		popChangeAnno.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popChangeAnnoClick(evt); } });
		popDelSen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) { popDelSenClick(evt); } });
	}

	public void netViewResized(ComponentEvent evt) {
		resizedResponse();
	}
	
	public void netViewMouseClicked(MouseEvent evt) { 
		if (evt.getButton() == MouseEvent.BUTTON3) {
			clickNode = getNodeAt(evt.getX(), evt.getY());
			if (clickNode != null) {
				if (clickNode.getNodeType()==TNode.SENSOR) {
					currentX = evt.getX();
					currentY = evt.getY();
					popSensor.show(evt.getComponent(), evt.getX(), evt.getY());
				}
			} else {
				popNetwork.show(evt.getComponent(), evt.getX(), evt.getY());
	   		}
	   	}
	}
	
	public void netViewMousePressed(MouseEvent evt) {
		if (evt.getButton() == MouseEvent.BUTTON1) {
			clickNode = getNodeAt(evt.getX(), evt.getY());
			if (clickNode != null) draggingNode = true;
			else {//if (evt.isControlDown()) {
				movingView = true;
				xMoveView = evt.getX();
				yMoveView = evt.getY();
			}	
		}
	}
	
	public void netViewMouseReleased(MouseEvent evt) {
		if (deployingNode) {
			relocateNode(deployNode, evt.getX(), evt.getY());
			deployingNode = false;
		}
		if (draggingNode && clickNode != null) {
			relocateNode(clickNode, evt.getX(), evt.getY());
			draggingNode = false;
			clickNode = null;
		}
		if (movingView) {
			movingView = false;
			double dx = -evt.getX() + xMoveView;
			double dy = evt.getY() - yMoveView;
			xZoom += dx/ratioW;
			if (xZoom < netW/2) xZoom = netW/2;
      if (xZoom+netW/2 > network.getDepW()) xZoom = network.getDepW()-netW/2;
      yZoom += dy/ratioH;
      if (yZoom < netH/2) yZoom = netH/2;
      if (yZoom+netH/2 > network.getDepH()) yZoom = network.getDepH()-netH/2;
			repaint();
		}
	}
	
	public void netViewMouseDragged(MouseEvent evt) {
		if (clickNode != null) {
			Graphics gr = getGraphics();
			gr.setXORMode(Color.white);
			gr.setColor(Color.black);
			gr.drawLine(xM_Old-10, yM_Old, xM_Old+10, yM_Old);
			gr.drawLine(xM_Old, yM_Old-10, xM_Old, yM_Old+10);
			gr.drawLine(evt.getX()-10, evt.getY(), evt.getX()+10, evt.getY());
			gr.drawLine(evt.getX(), evt.getY()-10, evt.getX(), evt.getY()+10);
			xM_Old = evt.getX();
			yM_Old = evt.getY();
		}
	}
	
	public void netViewMouseMoved(MouseEvent evt) {
		if (deployingNode) {
			Graphics gr = this.getGraphics();
			gr.setXORMode(Color.white);
			gr.setColor(Color.black);
			gr.drawLine(xM_Old-10, yM_Old, xM_Old+10, yM_Old);
			gr.drawLine(xM_Old, yM_Old-10, xM_Old, yM_Old+10);
			gr.drawLine(evt.getX()-10, evt.getY(), evt.getX()+10, evt.getY());
			gr.drawLine(evt.getX(), evt.getY()-10, evt.getX(), evt.getY()+10);
			xM_Old = evt.getX();
			yM_Old = evt.getY();
		}
	}
	
	public void netViewMouseWheeled(MouseWheelEvent evt) {
		if (evt.getScrollAmount() != 0) {
      double zoom = (evt.getWheelRotation() < 0)?0.9:1.1;
      netW *= zoom;
      if (netW > network.getDepW())
      	netW = network.getDepW();
      netH *= zoom;
      if (netH > network.getDepH())
      	netH = network.getDepH();
      if (xZoom < netW/2) xZoom = netW/2;
      if (xZoom+netW/2 > network.getDepW()) xZoom = network.getDepW()-netW/2;
      if (yZoom < netH/2) yZoom = netH/2;
      if (yZoom+netH/2 > network.getDepH()) yZoom = network.getDepH()-netH/2;
      ratioW = (double)this.getBounds().width/netW;
			ratioH = (double)this.getBounds().height/netH;
			repaint();
    } 
	}
	
	public void popNewSensorsClick(ActionEvent evt) {
		String ss = JOptionPane.showInputDialog(this, "How many nodes?", "", JOptionPane.QUESTION_MESSAGE);
		if (ss != null) {
			int nnode = Integer.valueOf(ss);
			if (nnode > 1) {
				for (int i=0; i<nnode; i++) {
					TSensor s = network.newSensor();
					s.setLoc(random.nextInt((int)network.getDepW()-s.getDeco().radius/2)+s.getDeco().radius/2, random.nextInt((int)network.getDepH()-s.getDeco().radius/2)+s.getDeco().radius/2);
				}
			} else {
				deployingNode = true;
		  	TSensor sensor = network.newSensor();
				deployNode = (TNode)sensor;
				sensor.setLoc(0, 0);
			}
			repaint();
		}
	}
	
	public void popSaveSceneClick(ActionEvent evt) {
		JFileChooser fc = new JFileChooser();
		String scenarioName = null;
		fc.setCurrentDirectory(new File("."));
		fc.setDialogTitle("Enter scenario name to save...");
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
	  String[] sce = new String[] {"sce"};
    fc.addChoosableFileFilter(new ExtFilter(sce, "Sensor profiles (*.sce)"));
		if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
			try {
				scenarioName = fc.getSelectedFile().getCanonicalPath();
				//System.out.println("Full paht:"+scenarioName);
				if (!"sce".equals((new TString(scenarioName,'.').getStrAt(1))))
					scenarioName += ".sce";
				BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(scenarioName)));
				writer.write("Deployment Area;"+network.getDepW()+";"+network.getDepH()+"\n");
				writer.write("Node quantity;"+net.len()+"\n");
				writer.write("Transmission range;"+dF.format(TRadio.DF_RTX).toString()+"\n");
				writer.write("Sensing range;"+dF.format(TSenInf.DF_RSEN).toString()+"\n");
				for (int i=0; i<net.len(); i++) {
					TNode n = (TNode)net.get(i);
					String x = dF.format(n.getLoc().x).toString();
					String y = dF.format(n.getLoc().y).toString();
					TSensor s; 
					if (n.getNodeType() == TNode.SENSOR) {
						s = (TSensor)n;
						writer.write("Sensor;"+x+";"+y);
						if (s.getType()==TSensor.DESTINATION)
							writer.write(";des");
						writer.write("\n");	
					} else {
						writer.write("Gateway;"+x+";"+y+"\n");
					}
				}
				writer.close();
			} catch (IOException ie) {}
		}
	}
	
	public void popLoadSceneClick(ActionEvent evt) {
		try {
			JFileChooser fc = new JFileChooser();
			String scenarioName = null;
			fc.setCurrentDirectory(new File("."));
			fc.setDialogTitle("Select scenario to open");
			fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
			String[] sce = new String[] {"sce"};
		  fc.addChoosableFileFilter(new ExtFilter(sce, "Sensor profiles (*.sce)"));
			if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
				scenarioName = fc.getSelectedFile().getCanonicalPath();
				TGateway.resetID();
				TSensor.resetID();
				network.reset();
				net = network.getNetwork();
				BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(scenarioName)));
				String line = null;
				TString st = new TString(';');
				
				//Dep. Area
				st.setStr(reader.readLine());
				int depW = (int)(Double.parseDouble(st.getStrAt(1)));
				int depH = (int)(Double.parseDouble(st.getStrAt(2)));
				netW = depW;
				netH = depH;
				xZoom = netW / 2;
				yZoom = netH / 2;
				network.setDepW(depW);
				network.setDepH(depH);
				
				//Number of nodes
				st.setStr(reader.readLine());
				int nnode = Integer.valueOf(st.getStrAt(1));
				
				//Comm & Sen ranges
				st.setStr(reader.readLine());
				TRadio.DF_RTX = Double.valueOf(st.getStrAt(1));
				st.setStr(reader.readLine());
				TSenInf.DF_RSEN = Double.valueOf(st.getStrAt(1));
				
				for (int i=0; i<nnode; i++) {
					st.setStr(reader.readLine());
					if ((st.getStrAt(0)).equalsIgnoreCase("sensor")) {
						double x = Double.valueOf(st.getStrAt(1));
						double y = Double.valueOf(st.getStrAt(2));
						TSensor s = network.newSensor();
						s.setLoc(x, y);
						if (st.len() > 3) {
							String des = st.getStrAt(3);
							if ("des".equalsIgnoreCase(des))
								s.setType(TSensor.DESTINATION);
						}
					} else {
						double x = Double.valueOf(st.getStrAt(1));
						double y = Double.valueOf(st.getStrAt(2));
						TGateway gw = network.newGateway();
						gw.setLoc(x, y);
					}
				}
				reader.close();
				resizedResponse();
				network.getCtrlView().txtDepArea.setText(String.valueOf(depW)+"x"+String.valueOf(depH));
				network.getCtrlView().txtSenRange.setText(dF.format(TSenInf.DF_RSEN).toString());
				network.getCtrlView().txtCommRange.setText(dF.format(TRadio.DF_RTX).toString());
			} 
		} catch (IOException ie) {}
	}
	
	public void popNewGatewayClick(ActionEvent evt) {
		deployingNode = true;
		TGateway gateway = network.newGateway();
		gateway.setLoc(0, 0);
		deployNode = (TNode)gateway;
	}
	
	public void popSetDesClick(ActionEvent evt) {	
		TSensor s = (TSensor)clickNode;
		s.setType(TSensor.DESTINATION);
		repaint();
	}
	
	public void popSetNorClick(ActionEvent evt) {	
		TSensor s = (TSensor)clickNode;
		s.setType(TSensor.NORMAL);
		repaint();
	}
	
	public void popChangeAnnoClick(ActionEvent evt) {	}
	
	public void popDelSenClick(ActionEvent evt) {
		TSensor s = (TSensor)clickNode;
		network.delNode(s);
		repaint();
	}
	
	//-------------------------Graphic stuff-------------------------
	public void resizedResponse() {
		if (netW > network.getDepW())
     	netW = network.getDepW();
    if (netH > network.getDepH())
     	netH = network.getDepH();
		simW = this.getBounds().width;
		simH = this.getBounds().height;
		ratioW = (double)simW/netW;
		ratioH = (double)simH/netH;
		repaint();
	}
	
	public TNode getNodeAt(int simX, int simY) {
		TNode rs = null;
		double X = (double)simX/ratioW;
		double Y = netH - (double)simY/ratioH;
		double realX = xZoom - netW/2 + X;
		double realY = yZoom - netH/2 + Y;
		for (int i=0; i<net.len(); i++) {
			TNode n = (TNode)net.get(i);
			if (n.inMe(new TPoint(realX, realY))) {
				rs = n;
				break;
			}
		}
		return rs;
	}
	
	/*public TPoint getNodeLoc(TNode n) {
		double realX = n.getLoc().x - xZoom + netW/2;
		double realY = n.getLoc().y - yZoom + netH/2;
		return new TPoint(realX*ratioW, realY*ratioH);
	}*/
	
	public void relocateNode(TNode n, int simX, int simY) {
		double X = (double)simX/ratioW;
		double Y = netH - (double)simY/ratioH;
		double realX = xZoom - netW/2 + X;
		double realY = yZoom - netH/2 + Y;
		n.setLoc(realX, realY);
		repaint();
	}
	
	public void drawNode(Graphics2D gr2d, TNode n) {
		double realX = n.getLoc().x - xZoom + netW/2;
		double realY = n.getLoc().y - yZoom + netH/2;
		int simX = (int)(realX*ratioW);
		int simY = (int)((netH-realY)*ratioH);
		TDeco deco = n.getDeco();
		int simRadW = (int)(deco.radius*ratioW);
		int simRadH = (int)(deco.radius*ratioH);
		deco.font = new Font("Arial", Font.BOLD, simRadW);
		FontMetrics fm = getFontMetrics(deco.font); 
		gr2d.setFont(deco.font);
		int sw;
		switch (n.getNodeType()) {
			case TNode.SENSOR:
				TSensor sensor = (TSensor)n;
				gr2d.setColor(deco.fillColor);
				gr2d.fillOval(simX-simRadW, simY-simRadH, 2*simRadW, 2*simRadH);
				gr2d.setColor(deco.textColor);
				sw = fm.stringWidth(sensor.getRadio().getAddr());
				gr2d.drawString(sensor.getRadio().getAddr(), simX-sw/2, simY-simRadW);				
				break;
				
			case TNode.GATEWAY:
				TGateway gateway = (TGateway)n;
				int[] x = new int[3];
				int[] y = new int[3];
				x[0] = simX; y[0] = simY-simRadH;
				x[1] = simX-simRadW; y[1] = simY+simRadH;
				x[2] = simX+simRadW; y[2] = simY+simRadH;
				Polygon gwShape = new Polygon(x, y, 3);
				gr2d.setColor(deco.fillColor);
				gr2d.fillPolygon(gwShape);
				gr2d.setColor(deco.textColor);
				sw = fm.stringWidth(gateway.getRadio().getAddr());
				gr2d.drawString(gateway.getRadio().getAddr(), simX-sw/2, simY-simRadW);				
				break;
				
			default: break;
		}
	}

	@Override 
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		//Redraw my stuff
		Graphics2D gr2d = (Graphics2D)g;
		gr2d.setXORMode(Color.white);

		//Draw links
		gr2d.setColor(Color.blue);
		for (int i=0; i<net.len(); i++) {
			for (int j=i+1; j<net.len(); j++) {
				TNode ni = (TNode)net.get(i);
				TNode nj = (TNode)net.get(j);
				if (TPoint.dist(ni.getLoc(), nj.getLoc()) <= ni.getRadio().getTX()) {
					int Xi = (int)((ni.getLoc().x-xZoom+netW/2)*ratioW);
					int Yi = (int)((netH-(ni.getLoc().y-yZoom+netH/2))*ratioH);
					int Xj = (int)((nj.getLoc().x-xZoom+netW/2)*ratioW);
					int Yj = (int)((netH-(nj.getLoc().y-yZoom+netH/2))*ratioH);
					gr2d.drawLine(Xi, Yi, Xj, Yj);
				}
			}
		}

		//Draw nodes
		for (int i=0; i<net.len(); i++) {
			TNode n = (TNode)net.get(i);
			drawNode(gr2d, n);
		}
	}
}
